package org.papervision3d.core.geom
{
    import flash.display.*;
    import flash.geom.Matrix;

    import org.papervision3d.core.*;
    import org.papervision3d.materials.*;
    import org.papervision3d.core.*;
    import org.papervision3d.core.proto.*;
    import org.papervision3d.core.geom.*;
    import flash.geom.Point;

    public class DrawTriangle
    {
        public var material:MaterialObject3D;
        public var v0:Vertex2D;
        public var v1:Vertex2D;
        public var v2:Vertex2D;
        public var uv0:NumberUV;
        public var uv1:NumberUV;
        public var uv2:NumberUV;
        public var minZ:Number;
        public var maxZ:Number;
        public var screenZ:Number;
        public var width:Number;
        public var height:Number;
        public var maxdim:Number;
        public var area:Number;
        public var centerx:Number;
        public var centery:Number;
        
        public var Zoffset : Number = 0;

        public var texturemapping:TextureMapping;
        
        public var smooth : Boolean = true;
		
		function DrawTriangle() {
		}
		
        public function render(container:Sprite):void
        {
            var graphics:Graphics = container.graphics;

            var texture:BitmapData = material.bitmap;
            var fillAlpha:Number = material.fillAlpha;
            var lineAlpha:Number = material.lineAlpha;
    
            // Line color
            if (lineAlpha)
                graphics.lineStyle(0, material.lineColor, lineAlpha);
            else
                graphics.lineStyle();
    
            if (texture)
            {
                var map:TextureMapping = texturemapping || transformUV();
    
                var a1:Number = map.a;
                var b1:Number = map.b;
                var c1:Number = map.c;
                var d1:Number = map.d;
                var tx1:Number = map.tx;
                var ty1:Number = map.ty;
    
                var a2:Number = v1.x - v0.x;
                var b2:Number = v1.y - v0.y;
                var c2:Number = v2.x - v0.x;
                var d2:Number = v2.y - v0.y;
                                       
                var matrix:Matrix = new Matrix(a1*a2 + b1*c2, a1*b2 + b1*d2, c1*a2 + d1*c2, c1*b2 + d1*d2,
                            tx1*a2 + ty1*c2 + v0.x, tx1*b2 + ty1*d2 + v0.y);
    
                graphics.beginBitmapFill(texture, matrix, false, false );//material.smooth && smooth );
            }
            else 
            if (fillAlpha)
            {
                graphics.beginFill(material.fillColor, fillAlpha);
            }
    
            // Draw triangle
            graphics.moveTo(v0.x, v0.y);
            graphics.lineTo(v1.x, v1.y);
            graphics.lineTo(v2.x, v2.y);
    
            if (lineAlpha)
                graphics.lineTo(v0.x, v0.y);
    
            if (texture || fillAlpha)
                graphics.endFill();
        }

        public function transformUV():TextureMapping
        {
            var w:Number = material.bitmap.width * material.maxU;
            var h:Number = material.bitmap.height * material.maxV;
    
            var u0:Number = w * uv0.u;
            var v0:Number = h * (1 - uv0.v);
            var u1:Number = w * uv1.u;
            var v1:Number = h * (1 - uv1.v);
            var u2:Number = w * uv2.u;
            var v2:Number = h * (1 - uv2.v);
    
            // Fix perpendicular projections
            if ((u0 == u1 && v0 == v1) || (u0 == u2 && v0 == v2))
            {
                u0 -= (u0 > 0.05) ? 0.05 : -0.05;
                v0 -= (v0 > 0.07) ? 0.07 : -0.07;
            }
    
            if (u2 == u1 && v2 == v1)
            {
                u2 -= (u2 > 0.05) ? 0.04 : -0.04;
                v2 -= (v2 > 0.06) ? 0.06 : -0.06;
            }
    
            var m:Matrix = new Matrix(u1 - u0, v1 - v0, u2 - u0, v2 - v0, u0, v0);
            m.invert();
    
            var mapping:TextureMapping = new TextureMapping();
            mapping.a = m.a;
            mapping.b = m.b;
            mapping.c = m.c;
            mapping.d = m.d;
            mapping.tx = m.tx;
            mapping.ty = m.ty;
            return mapping;
        }
        

        public function maxEdgeSqr():Number
        {
            var d01:Number = Vertex2D.distanceSqr(v0, v1);
            var d12:Number = Vertex2D.distanceSqr(v1, v2);
            var d20:Number = Vertex2D.distanceSqr(v2, v0);
            return Math.max(Math.max(d01, d12), d20);
        }

        public function minEdgeSqr():Number
        {
            var d01:Number = Vertex2D.distanceSqr(v0, v1);
            var d12:Number = Vertex2D.distanceSqr(v1, v2);
            var d20:Number = Vertex2D.distanceSqr(v2, v0);
            return Math.min(Math.min(d01, d12), d20);
        }

        public function maxDistortSqr(focus:Number):Number
        {
            var d01:Number = Vertex2D.distortSqr(v0, v1, focus);
            var d12:Number = Vertex2D.distortSqr(v1, v2, focus);
            var d20:Number = Vertex2D.distortSqr(v2, v0, focus);
            return Math.max(Math.max(d01, d12), d20);
        }

        public function minDistortSqr(focus:Number):Number
        {
            var d01:Number = Vertex2D.distortSqr(v0, v1, focus);
            var d12:Number = Vertex2D.distortSqr(v1, v2, focus);
            var d20:Number = Vertex2D.distortSqr(v2, v0, focus);
            return Math.min(Math.min(d01, d12), d20);
        }

        public function acuteAngled():Boolean
        {
            var d01:Number = Vertex2D.distanceSqr(v0, v1);
            var d12:Number = Vertex2D.distanceSqr(v1, v2);
            var d20:Number = Vertex2D.distanceSqr(v2, v0);
            var dd01:Number = d01 * d01;
            var dd12:Number = d12 * d12;
            var dd20:Number = d20 * d20;
            return (dd01 <= dd12 + dd20) && (dd12 <= dd20 + dd01) && (dd20 <= dd01 + dd12);
        }

        public function bisect(focus:Number):Array
        {
            var d01:Number = Vertex2D.distanceSqr(v0, v1);
            var d12:Number = Vertex2D.distanceSqr(v1, v2);
            var d20:Number = Vertex2D.distanceSqr(v2, v0);

            if ((d12 >= d01) && (d12 >= d20))
                return bisect12(focus);
            else
            if (d01 >= d20)
                return bisect01(focus);
            else
                return bisect20(focus);
        }

        public function zbisect(focus:Number):Array
        {
            var d01:Number = Math.abs(v0.z - v1.z);
            var d12:Number = Math.abs(v1.z - v2.z);
            var d20:Number = Math.abs(v2.z - v0.z);

            if ((d12 >= d01) && (d12 >= d20))
                return bisect12(focus);
            else
            if (d01 >= d20)
                return bisect01(focus);
            else
                return bisect20(focus);
        }

        public function zplbisect(focus:Number):Array
        {
            var d01:Number = Vertex2D.distortSqr(v0, v1, focus);
            var d12:Number = Vertex2D.distortSqr(v1, v2, focus);
            var d20:Number = Vertex2D.distortSqr(v2, v0, focus);

            if ((d12 >= d01) && (d12 >= d20))
                return bisect12(focus);
            else
            if (d01 >= d20)
                return bisect01(focus);
            else
                return bisect20(focus);
        }

        private function bisect01(focus:Number):Array
        {
            var v01:Vertex2D = Vertex2D.median(v0, v1, focus);
            var uv01:NumberUV = NumberUV.median(uv0, uv1);
            return new Array(
                create(material, v2, v0, v01, uv2, uv0, uv01),
                create(material, v01, v1, v2, uv01, uv1, uv2) 
            );
        }

        private function bisect12(focus:Number):Array
        {
            var v12:Vertex2D = Vertex2D.median(v1, v2, focus);
            var uv12:NumberUV = NumberUV.median(uv1, uv2);
            return new Array(
                create(material, v0, v1, v12, uv0, uv1, uv12),
                create(material, v12, v2, v0, uv12, uv2, uv0) 
            );
        }

        private function bisect20(focus:Number):Array
        {
            var v20:Vertex2D = Vertex2D.median(v2, v0, focus);
            var uv20:NumberUV = NumberUV.median(uv2, uv0);
            return new Array(
                create(material, v1, v2, v20, uv1, uv2, uv20),
                create(material, v20, v0, v1, uv20, uv0, uv1) 
            );                                                
        }

        public function quarter(focus:Number):Array
        {
            var v01:Vertex2D = Vertex2D.median(v0, v1, focus);
            var v12:Vertex2D = Vertex2D.median(v1, v2, focus);
            var v20:Vertex2D = Vertex2D.median(v2, v0, focus);
            var uv01:NumberUV = NumberUV.median(uv0, uv1);
            var uv12:NumberUV = NumberUV.median(uv1, uv2);
            var uv20:NumberUV = NumberUV.median(uv2, uv0);

            return new Array(
                create(material, v0, v01, v20, uv0, uv01, uv20),
                create(material, v1, v12, v01, uv1, uv12, uv01),
                create(material, v2, v20, v12, uv2, uv20, uv12),
                create(material, v01, v12, v20, uv01, uv12, uv20)
            );
        }

        public function inside(x:Number, y:Number):Boolean
        {   
            if (v0.x*(y - v1.y) + v1.x*(v0.y - y) + x*(v1.y - v0.y) < 0)
                return false;

            if (v0.x*(v2.y - y) + x*(v0.y - v2.y) + v2.x*(y - v0.y) < 0)
                return false;

            if (x*(v2.y - v1.y) + v1.x*(y - v2.y) + v2.x*(v1.y - y) < 0)
                return false;

            return true;
        }

        public function distanceToCenter(x:Number, y:Number):Number
        {   
            return Math.sqrt((centerx-x)*(centerx-x) + (centery-y)*(centery-y));
        }
        
        public function UVatPoint( x : Number, y : Number ) : Object {
        	
        	var v0_x : Number = v2.x - v0.x;
        	var v0_y : Number = v2.y - v0.y;
        	var v1_x : Number = v1.x - v0.x;
        	var v1_y : Number = v1.y - v0.y;
        	var v2_x : Number = x - v0.x;
        	var v2_y : Number = y - v0.y;
        	
        	var dot00 : Number = v0_x * v0_x + v0_y * v0_y;
        	var dot01 : Number = v0_x * v1_x + v0_y * v1_y;
        	var dot02 : Number = v0_x * v2_x + v0_y * v2_y;
        	var dot11 : Number = v1_x * v1_x + v1_y * v1_y;
        	var dot12 : Number = v1_x * v2_x + v1_y * v2_y;
        	
        	var invDenom : Number = 1 / (dot00 * dot11 - dot01 * dot01);
			var u : Number = (dot11 * dot02 - dot01 * dot12) * invDenom;
			var v : Number = (dot00 * dot12 - dot01 * dot02) * invDenom;
        	
        	return { u : u, v : v };
        }
        
         
        public function getMapCoordAtPoint( x : Number, y : Number ) : Point {
        	if( material.bitmap == null ) return new Point();
        	
        	var UV : Object = UVatPoint(x,y);
			var u : Number = UV.u;
			var v : Number = UV.v;
        	
        	var v_x : Number = ( uv1.u - uv0.u ) * v +  (uv2.u - uv0.u) * u + uv0.u;
        	var v_y : Number = ( uv1.v - uv0.v ) * v +  (uv2.v - uv0.v) * u + uv0.v;
        	
        	return new Point( v_x * material.bitmap.width, material.bitmap.height - v_y * material.bitmap.height );
        	
        	
        }

        private static function create(material:MaterialObject3D, v0:Vertex2D, v1:Vertex2D, v2:Vertex2D, uv0:NumberUV, uv1:NumberUV, uv2:NumberUV):DrawTriangle
        {
            var tri:DrawTriangle = new DrawTriangle();
            tri.material = material;
            tri.v0 = v0;
            tri.v1 = v1;
            tri.v2 = v2;
            tri.uv0 = uv0;
            tri.uv1 = uv1;
            tri.uv2 = uv2;
            tri.calc();
            return tri;
        }

        public function calc():void
        {
            minZ = Math.min(v0.z, Math.min(v1.z, v2.z));
            maxZ = Math.max(v0.z, Math.max(v1.z, v2.z));
            screenZ = (v0.z + v1.z + v2.z) / 3 - Zoffset;
            width = Math.max(Math.abs(v0.x - v1.x), Math.max(Math.abs(v1.x - v2.x), Math.abs(v2.x - v0.x)));
            height = Math.max(Math.abs(v0.y - v1.y), Math.max(Math.abs(v1.y - v2.y), Math.abs(v2.y - v0.y)));
            maxdim = Math.max(width, height);
            area = 0.5 * (v0.x*(v2.y - v1.y) + v1.x*(v0.y - v2.y) + v2.x*(v1.y - v0.y));
            centerx = (v0.x + v1.x + v2.x) / 3;
            centery = (v0.y + v1.y + v2.y) / 3;
            //smooth = screenZ < 20000;
        }

    }
}
